﻿using Sasa.QualityTools.CodeAnalysis.Core.Properties;
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using Sasa.QualityTools.CodeAnalysis.Core.Entities.CodeAnalysis;

namespace Sasa.QualityTools.CodeAnalysis.Core.Services.CodeAnalysis
{
    internal class FxCopReport
    {
        internal IFxCopReportValueConverter Converter { get; set; }

        internal ICollection<Problem> Problems { get; private set; }

        internal ICollection<Rule> Rules { get; private set; }


        internal FxCopReport()
        {
            Converter = new DefaultFxCopReportValueConverter();
            Problems = new List<Problem>();
            Rules = new List<Rule>();
        }


        internal void Load(string path)
        {
            if (path == null) throw new ArgumentNullException("path");
            if (!File.Exists(path)) throw new ArgumentException("The specified file (" + path + ") doesn't exist.", "path");

            XmlDocument document = new XmlDocument();
            try
            {
                document.Load(path);
            }
            catch (Exception e)
            {
                throw new ArgumentException("Failed to load the specified FxCop report (" + path+").", e);
            }

            foreach (XmlNode node in document.SelectNodes("//Message"))
            {
                foreach (Problem problem in GetProblemsFrom(node))
                {
                    Problems.Add(problem);
                }
            }
            foreach (XmlNode node in document.SelectNodes("/FxCopReport/Rules/Rule"))
            {
                Rules.Add(GetRuleFrom(node));
            }
        }

        /// <summary>
        /// Get the collection of problems from the specified Message node of the FxCop report.
        /// </summary>
        /// <param name="messageNode">The Message node of the FxCop report.</param>
        /// <returns>The collection of problems.</returns>
        internal List<Problem> GetProblemsFrom(XmlNode messageNode)
        {
            Problem template = new Problem();
            template.CheckId = messageNode.Attributes["CheckId"].Value;
            template.Category = messageNode.Attributes["Category"].Value;
            template.Id = messageNode.SelectSingleNode("../../@Name").Value;
            template.Kind = GetCodeKind(messageNode);

            if (IsType(template.Kind))
            {
                string ns = messageNode.SelectSingleNode("ancestor::Namespace/@Name").Value;
                template.TypeName = String.Format("{0}.{1}", ns, template.Id);
            }
            else if (IsMember(template.Kind))
            {
                string ns = messageNode.SelectSingleNode("ancestor::Namespace/@Name").Value;
                string type = messageNode.SelectSingleNode("ancestor::Type/@Name").Value;
                template.TypeName = String.Format("{0}.{1}", ns, type);
            }

            XmlNode targetNameNode = messageNode.SelectSingleNode("ancestor::Target/@Name");
            if (targetNameNode != null)
            {
                template.AssemblyPath = Converter.ConvertTargetPath(targetNameNode.Value);
            }

            List<Problem> problems = new List<Problem>();
            foreach (XmlNode node in messageNode.SelectNodes("Issue"))
            {
                Problem problem = template.Copy();
                if (node.Attributes["Path"] != null && node.Attributes["File"] != null)
                {
                    problem.Path = Path.Combine(node.Attributes["Path"].Value, node.Attributes["File"].Value);
                }
                if (node.Attributes["Line"] != null)
                {
                    problem.Line = Int32.Parse(node.Attributes["Line"].Value);
                }
                problem.Message = node.InnerText;
                problems.Add(problem);
            }

            return problems;
        }

        private CodeKind GetCodeKind(XmlNode messageNode)
        {
            CodeKind kind = CodeKind.Unknown;

            XmlNode kindAttr = messageNode.SelectSingleNode("../../@Kind");
            if (kindAttr != null)
            {
                Enum.TryParse<CodeKind>(kindAttr.Value, true, out kind);
            }
            else
            {
                Enum.TryParse<CodeKind>(messageNode.SelectSingleNode("../..").Name, true, out kind);
            }

            return kind;
        }

        private bool IsType(CodeKind kind)
        {
            return kind == CodeKind.Class || kind == CodeKind.Delegate || kind == CodeKind.Enum || kind == CodeKind.Interface || kind == CodeKind.Struct;
        }

        private bool IsMember(CodeKind kind)
        {
            return kind == CodeKind.Constructor || kind == CodeKind.Event || kind == CodeKind.Field || kind == CodeKind.Method || kind == CodeKind.Property;
        }

        /// <summary>
        /// Get the rule metadata from the specified Rule node of the FxCop report.
        /// </summary>
        /// <param name="ruleNode">The Rule node of the FxCop report.</param>
        /// <returns>The rule metadata.</returns>
        internal Rule GetRuleFrom(XmlNode ruleNode)
        {
            Rule rule = new Rule();

            rule.CheckId = ruleNode.Attributes["CheckId"].Value;
            rule.Category = ruleNode.Attributes["Category"].Value;
            rule.Name = ruleNode["Name"].InnerText;
            rule.Description = ruleNode["Description"].InnerText;

            return rule;
        }
    }
}
